/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {ArkFile, FullPosition, Local, Stmt} from "arkanalyzer/lib";
import Logger, {LOG_MODULE_TYPE} from 'arkanalyzer/lib/utils/logger';
import {BaseChecker, BaseMetaData} from "../BaseChecker";
import {Defects} from "../../Index";
import {
    ClassMatcher,
    FileMatcher,
    MatcherCallback,
    MatcherTypes,
    MethodMatcher,
    NamespaceMatcher
} from "../../Index";
import {Rule} from "../../Index";
import {ArkAssignStmt, ArkClass, ArkMethod, ArkNamespace} from "arkanalyzer";
import {ClassCategory} from "arkanalyzer/lib/core/model/ArkClass";
import {RuleListUtil} from "../../utils/common/DefectsList";
import { IssueReport } from "../../model/Defects";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoRedeclareCheck');

const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-redeclare-check.md",
    description: "Disallow variable redeclaration."
};

type Options = [{
    builtinGlobals: boolean,
    ignoreDeclarationMerge: boolean
}]

export class NoRedeclareCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private readonly defaultTypes = ['String', 'Boolean', 'Number', 'Symbol', 'BigInt', 'Function', 'Object'];
    private builtinGlobals = true;
    private ignoreDeclarationMerge = false;

    check(target: any) {};

    private nsMatcher: NamespaceMatcher = {
        matcherType: MatcherTypes.NAMESPACE
    };
    private clsMatcher: ClassMatcher = {
        matcherType: MatcherTypes.CLASS
    };
    private mtdMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
    };
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };

    public registerMatchers(): MatcherCallback[] {
        if (this.rule && this.rule.option) {
            const option = this.rule.option as Options;
            if (option.length > 0) {
                this.builtinGlobals = option[0].builtinGlobals;
                this.ignoreDeclarationMerge = option[0].ignoreDeclarationMerge;
            }
        }

        const nsMatchBuildCb: MatcherCallback = {
            matcher: this.nsMatcher,
            callback: this.checkNameSpace
        }
        const classMatchBuildCb: MatcherCallback = {
            matcher: this.clsMatcher,
            callback: this.checkClass
        }
        const mthMatchBuildCb: MatcherCallback = {
            matcher: this.mtdMatcher,
            callback: this.checkMethod
        }
        const fileMatchBuildCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.checkFile
        }
        return [nsMatchBuildCb, classMatchBuildCb, mthMatchBuildCb, fileMatchBuildCb];
    }

    // 命名空间
    public checkNameSpace = (targetNS: ArkNamespace) => {
        // 检查命名空间中的变量
        this.checkVarsRedeclareInNameSpace(targetNS);

        let targetName = targetNS.getNamespaceSignature().getNamespaceName();
        if (this.isBuildin(targetName)) {
            this.addNamespaceIssueReport(targetNS, true);
            return;
        }

        if (this.isNameSpaceRedeclare(targetNS)) {
            this.addNamespaceIssueReport(targetNS, false);
            return;
        }
    }

    private isNameSpaceRedeclare(targetNS: ArkNamespace): boolean {
        let declaringInstance = targetNS.getDeclaringInstance();

        if (!this.ignoreDeclarationMerge) {
            // 命名空间+命名空间
            let namespaces = declaringInstance.getNamespaces();
            for (const namespace of namespaces) {

                if (namespace.getName() == targetNS.getName() &&
                    (namespace.getLine() != targetNS.getLine() || namespace.getColumn() != targetNS.getColumn())) {
                    return true;
                }
            }

            // 命名空间+类、命名空间+接口、命名空间+枚举
            let classes = declaringInstance.getClasses();
            for (const cls of classes) {
                if (cls.getName() == '%dflt') {
                    continue;
                }

                if (cls.getCategory() != ClassCategory.CLASS &&
                    cls.getCategory() != ClassCategory.INTERFACE &&
                    cls.getCategory() != ClassCategory.ENUM) {
                    continue;
                }

                if (cls.getName() == targetNS.getName()) {
                    return true;
                }
            }

            // 命名空间+方法
            let methods = declaringInstance.getDefaultClass().getMethods();
            for (const method of methods) {
                if (method.getName() == '%dflt') {
                    continue;
                }

                if (method.getName() == targetNS.getName()) {
                    return true;
                }
            }
        }

        // 命名空间+变量
        let locals = declaringInstance.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals();
        if (locals != undefined) {
            for (const local of locals.values()) {
                if (local.getName() == targetNS.getName()) {
                    return true;
                }
            }
        }

        return false;
    }

    private addNamespaceIssueReport(targetNS: ArkNamespace, buildin: boolean) {
        let name = targetNS.getNamespaceSignature().getNamespaceName();
        let message = "'" + name + "' is already defined" + (buildin ? " as a built-in global variable." : ".");
        let startCol = targetNS.getColumn() + this.getNameIndex(targetNS.getCode(), 'namespace');
        this.addIssueReport(targetNS.getDeclaringArkFile(), targetNS.getLine(), startCol, startCol + name.length, message);
    }

    // 类、接口、枚举
    public checkClass = (targetClass: ArkClass) => {
        // 检查类/接口/枚举中的变量
        this.checkVarsRedeclareInClass(targetClass);

        let targetName = targetClass.getName();
        if (targetName == '%dflt') {
            return;
        }

        if (this.isBuildin(targetName)) {
            this.addClassIssueReport(targetClass, true);
            return;
        }

        if (this.isClassRedeclare(targetClass)) {
            this.addClassIssueReport(targetClass, false);
            return;
        }
    }

    private isClassRedeclare(targetClass: ArkClass): boolean {
        let classSignature = targetClass.getSignature();
        let className = classSignature.getClassName();
        if (className == '%dflt') {
            return false;
        }

        let category = targetClass.getCategory();
        let namespaceSignature = classSignature.getDeclaringNamespaceSignature();
        if (namespaceSignature) { // 该类在命名空间中
            let targetNamespace = targetClass.getDeclaringArkNamespace();
            if (targetNamespace == null) {
                return false;
            }
            if (!this.ignoreDeclarationMerge) {
                if (category == ClassCategory.CLASS) {
                    // 类+接口
                    let classes = targetNamespace.getClasses();
                    for (const clazz of classes) {
                        if (clazz.getCategory() == ClassCategory.INTERFACE && clazz.getName() == className) {
                            return true;
                        }
                    }

                    // 类+命名空间
                    let namespaces = targetNamespace.getNamespaces();
                    for (const namespace of namespaces) {
                        if (namespace.getName() == className) {
                            return true;
                        }
                    }
                } else if (category == ClassCategory.INTERFACE) {
                    // 接口+接口/类
                    let classes = targetNamespace.getClasses();
                    for (const clazz of classes) {
                        if ((clazz.getCategory() == ClassCategory.INTERFACE || clazz.getCategory() == ClassCategory.CLASS) &&
                            clazz.getName() == className &&
                            (targetClass.getLine() != clazz.getLine() || targetClass.getColumn() != clazz.getColumn())) {
                            return true;
                        }
                    }
                } else if (category == ClassCategory.ENUM) {
                    // 枚举+命名空间
                    let namespaces = targetNamespace.getNamespaces();
                    for (const namespace of namespaces) {
                        if (namespace.getName() == className) {
                            return true;
                        }
                    }
                }
            }
            if (category == ClassCategory.CLASS) {
                // 类+类/枚举
                let classes = targetNamespace.getClasses();
                for (const clazz of classes) {
                    if ((clazz.getCategory() == ClassCategory.INTERFACE || clazz.getCategory() == ClassCategory.ENUM) &&
                        clazz.getName() == className &&
                        (targetClass.getLine() != clazz.getLine() || targetClass.getColumn() != clazz.getColumn())) {
                        return true;
                    }
                }

                // 类+方法
                let methods = targetNamespace.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (className == method.getName()) {
                        return true;
                    }
                }
            } else if (category == ClassCategory.INTERFACE) {
                // 接口+枚举
                let classes = targetNamespace.getClasses();
                for (const clazz of classes) {
                    if (clazz.getCategory() == ClassCategory.ENUM && clazz.getName() == className) {
                        return true;
                    }
                }

                // 接口+方法
                let methods = targetNamespace.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (className == method.getName()) {
                        return true;
                    }
                }
            } else if (category == ClassCategory.ENUM) {
                // 枚举+枚举
                let classes = targetNamespace.getClasses();
                for (const clazz of classes) {
                    if (clazz.getCategory() == ClassCategory.ENUM && clazz.getName() == className &&
                        (targetClass.getLine() != clazz.getLine() || targetClass.getColumn() != clazz.getColumn())) {
                        return true;
                    }
                }

                // 枚举+方法
                let methods = targetNamespace.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (className == method.getName()) {
                        return true;
                    }
                }
            }

            // 类/接口/枚举+变量
            let locals = targetNamespace.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals();
            if (locals) {
                for (const local of locals?.values()) {
                    if (local.getName() == 'this') {
                        continue;
                    }

                    if (local.getName() == className) {
                        return true;
                    }
                }
            }
        } else { // 该类不在命名空间中，外层是文件
            // 在文件中查找
            const arkFile = targetClass.getDeclaringArkFile();
            if (!this.ignoreDeclarationMerge) {
                if (category == ClassCategory.CLASS) {
                    // 类+接口
                    let classes = arkFile.getClasses();
                    for (const clazz of classes) {
                        if (clazz.getCategory() == ClassCategory.INTERFACE && clazz.getName() == className) {
                            return true;
                        }
                    }

                    // 类+命名空间
                    let namespaces = arkFile.getNamespaces();
                    for (const namespace of namespaces) {
                        if (namespace.getName() == className) {
                            return true;
                        }
                    }
                } else if (category == ClassCategory.INTERFACE) {
                    // 接口+接口/类
                    let classes = arkFile.getClasses();
                    for (const clazz of classes) {
                        if ((clazz.getCategory() == ClassCategory.INTERFACE || clazz.getCategory() == ClassCategory.CLASS) &&
                            clazz.getName() == className &&
                            (targetClass.getLine() != clazz.getLine() || targetClass.getColumn() != clazz.getColumn())) {
                            return true;
                        }
                    }
                } else if (category == ClassCategory.ENUM) {
                    // 枚举+命名空间
                    let namespaces = arkFile.getNamespaces();
                    for (const namespace of namespaces) {
                        if (namespace.getName() == className) {
                            return true;
                        }
                    }
                }
            }
            if (category == ClassCategory.CLASS) {
                // 类+类/枚举
                let classes = arkFile.getClasses();
                for (const clazz of classes) {
                    if ((clazz.getCategory() == ClassCategory.INTERFACE || clazz.getCategory() == ClassCategory.ENUM) &&
                        clazz.getName() == className &&
                        (targetClass.getLine() != clazz.getLine() || targetClass.getColumn() != clazz.getColumn())) {
                        return true;
                    }
                }

                // 类+方法
                let methods = arkFile.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (className == method.getName()) {
                        return true;
                    }
                }
            } else if (category == ClassCategory.INTERFACE) {
                // 接口+枚举
                let classes = arkFile.getClasses();
                for (const clazz of classes) {
                    if (clazz.getCategory() == ClassCategory.ENUM && clazz.getName() == className) {
                        return true;
                    }
                }

                // 接口+方法
                let methods = arkFile.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (className == method.getName()) {
                        return true;
                    }
                }
            } else if (category == ClassCategory.ENUM) {
                // 枚举+枚举
                let classes = arkFile.getClasses();
                for (const clazz of classes) {
                    if (clazz.getCategory() == ClassCategory.ENUM && clazz.getName() == className &&
                        (targetClass.getLine() != clazz.getLine() || targetClass.getColumn() != clazz.getColumn())) {
                        return true;
                    }
                }

                // 枚举+方法
                let methods = arkFile.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (className == method.getName()) {
                        return true;
                    }
                }
            }

            // 类/接口/枚举+变量
            let locals = arkFile.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals();
            if (locals) {
                for (const local of locals?.values()) {
                    if (local.getName() == 'this') {
                        continue;
                    }

                    if (local.getName() == className) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private addClassIssueReport(targetClass: ArkClass, buildin: boolean) {
        let name = targetClass.getName();
        let message = "'" + name + "' is already defined" + (buildin ? " as a built-in global variable." : ".");
        let startCol = targetClass.getColumn() + this.getNameIndex(targetClass.getCode() ?? '', 'class');
        this.addIssueReport(targetClass.getDeclaringArkFile(), targetClass.getLine(), startCol, startCol + name.length, message);
    }

    // 方法
    private checkMethod = (targetMtd: ArkMethod) => {
        let targetName = targetMtd.getName();
        if (targetName == '%dflt') {
            return;
        }

        // 方法中的变量
        this.checkVarsRedeclareInMtd(targetMtd);

        if (this.isBuildin(targetName)) {
            this.addMtdIssueReport(targetMtd, true);
            return;
        }

        if (this.isMtdRedeclare(targetMtd)) {
            this.addMtdIssueReport(targetMtd, false);
            return;
        }
    }

    private isMtdRedeclare(targetMtd: ArkMethod): boolean {
        const targetName = targetMtd.getName();
        const targetClass = targetMtd.getDeclaringArkClass();
        if (targetClass.getName() == '%dflt') {
            const targetNamespace = targetClass.getDeclaringArkNamespace();
            if (targetNamespace) { // 命名空间中的方法
                if (!this.ignoreDeclarationMerge) {
                    // 方法+命名空间
                    let namespaces = targetNamespace.getNamespaces();
                    for (const namespace of namespaces) {
                        if (namespace.getName() == targetName) {
                            return true;
                        }
                    }
                }

                // 方法+接口/类/枚举
                let classes = targetNamespace.getClasses();
                for (const clazz of classes) {
                    if (clazz.getName() == targetName) {
                        return true;
                    }
                }

                // 方法+方法
                let methods = targetNamespace.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (targetName == method.getName() &&
                        (method.getLine() != targetMtd.getLine() || method.getColumn() != targetMtd.getColumn())) {
                        return true;
                    }
                }

                // 方法+变量
                let locals = targetNamespace.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals();
                if (locals) {
                    for (const local of locals?.values()) {
                        if (local.getName() == 'this') {
                            continue;
                        }

                        if (local.getName() == targetName) {
                            return true;
                        }
                    }
                }
            } else { // 文件中的方法
                let targetFile = targetMtd.getDeclaringArkFile();
                if (!this.ignoreDeclarationMerge) {
                    // 方法+命名空间
                    let namespaces = targetFile.getNamespaces();
                    for (const namespace of namespaces) {
                        if (namespace.getName() == targetName) {
                            return true;
                        }
                    }
                }

                // 方法+接口/类/枚举
                let classes = targetFile.getClasses();
                for (const clazz of classes) {
                    if (clazz.getName() == targetName) {
                        return true;
                    }
                }

                // 方法+方法
                let methods = targetFile.getDefaultClass().getMethods();
                for (const method of methods) {
                    if (method.getName() == '%dflt') {
                        continue;
                    }

                    if (targetName == method.getName() &&
                        (method.getLine() != targetMtd.getLine() || method.getColumn() != targetMtd.getColumn())) {
                        return true;
                    }
                }

                // 方法+变量
                let locals = targetFile.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals();
                if (locals) {
                    for (const local of locals?.values()) {
                        if (local.getName() == 'this') {
                            continue;
                        }

                        if (local.getName() == targetName) {
                            return true;
                        }
                    }
                }
            }
        } else { // 类中的方法
            // 方法+方法
            let methods = targetClass.getMethods();
            for (const method of methods) {
                if (method.getName() == '%dflt') {
                    continue;
                }

                if (targetName == method.getName() &&
                    (method.getLine() != targetMtd.getLine() || method.getColumn() != targetMtd.getColumn())) {
                    return true;
                }
            }

            // 方法+变量
            let fields = targetClass.getFields();
            for (const field of fields) {
                if (field.getName() == targetName) {
                    return true;
                }
            }
        }

        return false;
    }

    private addMtdIssueReport(targetMtd: ArkMethod, buildin: boolean) {
        let name = targetMtd.getName();
        let message = "'" + name + "' is already defined" + (buildin ? " as a built-in global variable." : ".");
        let startCol = (targetMtd.getColumn() ?? 0) + this.getNameIndex((targetMtd.getCode() ?? ''), 'function');
        this.addIssueReport(targetMtd.getDeclaringArkFile(), targetMtd.getLine() ?? 0, startCol, startCol + name.length, message);
    }

    // 检查文件中的变量是否重名
    public checkFile = (targetFile: ArkFile) => {
        // 文件中的变量
        let locals = targetFile.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals();
        this.checkVarRedeclareWithVars(locals);
    }

    private checkVarRedeclareWithVars(locals: Map<string, Local> | undefined) {
        if (locals == undefined) {
            return;
        }
        let values: Local[] = [];
        for (const value of locals.values()) {
            values.push(value);
        }
        for (const local of values) {
            const targetName = local.getName();
            if (targetName == 'this') {
                continue;
            }

            if (this.isBuildin(targetName)) {
                this.addVarIssueReport(local.getDeclaringStmt(), true);
            } else {
                // 当前变量和这个文件内的其他变量对比
                for (const other of values) {
                    const otherName = other.getName();
                    if (targetName == 'this') {
                        continue;
                    }

                    let targetLocal = this.getVarPosition(local.getDeclaringStmt());
                    if (targetLocal == null) {
                        continue;
                    }

                    let otherLocal = this.getVarPosition(other.getDeclaringStmt());
                    if (otherLocal == null) {
                        continue;
                    }

                    if (targetName == otherName && targetLocal != otherLocal) {
                        this.addVarIssueReport(local.getDeclaringStmt(), false);
                        break;
                    }
                }
            }
        }
    }

    // 命名空间中的变量
    private checkVarsRedeclareInNameSpace(targetNS: ArkNamespace) {
        let targets = targetNS.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals();
        this.checkVarRedeclareWithVars(targets);
    }

    // 类/接口/枚举中的变量
    private checkVarsRedeclareInClass(targetClass: ArkClass) {
        let targets = targetClass.getDefaultArkMethod()?.getBody()?.getLocals();
        this.checkVarRedeclareWithVars(targets);
    }

    // 方法中的变量
    private checkVarsRedeclareInMtd(targetMtd: ArkMethod) {
        let targets = targetMtd.getBody()?.getLocals();
        this.checkVarRedeclareWithVars(targets);
    }

    private addVarIssueReport(targetStmt: Stmt | null, buildin: boolean) {
        if ((targetStmt == null) || !(targetStmt instanceof ArkAssignStmt)) {
            return;
        }

        let leftOp = targetStmt.getLeftOp();
        if (!(leftOp instanceof Local)) {
            return;
        }

        let pos = this.getVarPosition(targetStmt);
        if (pos == null) {
            return;
        }

        let name = leftOp.getName();
        let message = "'" + name + "' is already defined" + (buildin ? " as a built-in global variable." : ".");
        this.addIssueReport(targetStmt.getCfg().getDeclaringMethod().getDeclaringArkFile(), pos.getFirstLine(), pos.getFirstCol(), pos.getLastCol(), message);
    }

    private getVarPosition(targetStmt: Stmt | null): FullPosition | null {
        if ((targetStmt == null) || !(targetStmt instanceof ArkAssignStmt)) {
            return null;
        }

        let leftOp = targetStmt.getLeftOp();
        if (!(leftOp instanceof Local)) {
            return null;
        }

        let pos = targetStmt.getOperandOriginalPosition(leftOp);
        return pos;
    }

    private isBuildin(keyword: string): boolean {
        if (this.builtinGlobals) {
            if (this.defaultTypes.includes(keyword)) {
                return true;
            }
        }
        return false;
    }

    // 获取名称的索引
    private getNameIndex(code: string, perfix: string): number {
        if (code.length <= perfix.length) {
            return 0;
        }

        if (!code.startsWith(perfix)) {
            return 0;
        }

        let otherCode = code.substring(perfix.length);
        let index = 0;
        for (index = 0; index < otherCode.length; index++) {
            let char = otherCode.substring(index, index + 1);
            if (char != ' ') {
                break;
            }
        }

        const nameStartIndex = perfix.length + index;
        return nameStartIndex;
    }

    private addIssueReport(arkFile: ArkFile, line: number, startCol: number, endCol: number, message: string) {
        // 过滤重复定义名称中，第一次定义的告警
        for (const def of this.issues) {
            if (def.defect.description != message) {
                continue;
            }

            if ((def.defect.reportLine > line) || (def.defect.reportLine == line && def.defect.reportColumn > startCol)) {
                return;
            }
        }

        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = arkFile.getFilePath();
        const fixKey = line + ':' + startCol;
        const mergeKey = filePath + '%' + fixKey + '%' + this.metaData.description.padEnd(130) + this.rule.ruleId;
        const defect = new Defects(line, startCol, message, severity, this.rule.ruleId,
            mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}